﻿//
// FixPointCS
//
// Copyright(c) Jere Sanisalo, Petri Kero
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
using System.Diagnostics;
using System.Runtime.CompilerServices;
using FixedPoint;

namespace System {

    [DebuggerStepThrough]
    [DebuggerNonUserCode]
    /// <summary>
    /// Signed 32.32 fixed point value struct.
    /// </summary>
    public struct F64 : IComparable<F64>, IEquatable<F64>, IComparable {

        // Constants
        public static F64 Neg1 { [MethodImpl(FixedUtil.AggressiveInlining)] get => FromRaw(Fixed64.Neg1); }
        public static F64 Zero { [MethodImpl(FixedUtil.AggressiveInlining)] get => FromRaw(Fixed64.Zero); }
        public static F64 Half { [MethodImpl(FixedUtil.AggressiveInlining)] get => FromRaw(Fixed64.Half); }
        public static F64 One { [MethodImpl(FixedUtil.AggressiveInlining)] get => FromRaw(Fixed64.One); }
        public static F64 Two { [MethodImpl(FixedUtil.AggressiveInlining)] get => FromRaw(Fixed64.Two); }
        public static F64 Hundred { [MethodImpl(FixedUtil.AggressiveInlining)] get => FromRaw(Fixed64.Hundred); }
        public static F64 Pi { [MethodImpl(FixedUtil.AggressiveInlining)] get => FromRaw(Fixed64.Pi); }
        public static F64 Pi2 { [MethodImpl(FixedUtil.AggressiveInlining)] get => FromRaw(Fixed64.Pi2); }
        public static F64 PiHalf { [MethodImpl(FixedUtil.AggressiveInlining)] get => FromRaw(Fixed64.PiHalf); }
        public static F64 E { [MethodImpl(FixedUtil.AggressiveInlining)] get => FromRaw(Fixed64.E); }

        public static F64 MinValue { [MethodImpl(FixedUtil.AggressiveInlining)] get => FromRaw(Fixed64.MinValue); }
        public static F64 MaxValue { [MethodImpl(FixedUtil.AggressiveInlining)] get => FromRaw(Fixed64.MaxValue); }

        // Raw fixed point value
        public readonly long Raw => m_Raw;
        private long m_Raw;

        // Construction
        [MethodImpl(FixedUtil.AggressiveInlining)] public static F64 FromRaw(long raw) { F64 v; v.m_Raw = raw; return v; }

        [MethodImpl(FixedUtil.AggressiveInlining)] public static F64 From(int @in) => FromRaw(Fixed64.FromInt(@in));
        [MethodImpl(FixedUtil.AggressiveInlining)] public static F64 From(float @in) => FromRaw(Fixed64.FromFloat(@in));
        [MethodImpl(FixedUtil.AggressiveInlining)] public static F64 From(double @in) => FromRaw(Fixed64.FromDouble(@in));

        [MethodImpl(MethodImplOptions.AggressiveInlining)] public static explicit operator F64(int @in) => From(@in);
        [MethodImpl(MethodImplOptions.AggressiveInlining)] public static explicit operator F64(float @in) => From(@in);
        [MethodImpl(MethodImplOptions.AggressiveInlining)] public static explicit operator F64(double @in) => From(@in);

        // Conversions
        [MethodImpl(FixedUtil.AggressiveInlining)] public static int FloorToInt(F64 a) => Fixed64.FloorToInt(a.m_Raw);
        [MethodImpl(FixedUtil.AggressiveInlining)] public static int CeilToInt(F64 a) => Fixed64.CeilToInt(a.m_Raw);
        [MethodImpl(FixedUtil.AggressiveInlining)] public static int RoundToInt(F64 a) => Fixed64.RoundToInt(a.m_Raw);
        public readonly float Float { [MethodImpl(FixedUtil.AggressiveInlining)] get => Fixed64.ToFloat(m_Raw); }
        public readonly double Double { [MethodImpl(FixedUtil.AggressiveInlining)] get => Fixed64.ToDouble(m_Raw); }
        public readonly int Int { [MethodImpl(FixedUtil.AggressiveInlining)] get => FloorToInt(this); }

        // Creates the fixed point number that's a divided by b.
        [MethodImpl(FixedUtil.AggressiveInlining)] public static F64 Ratio(int a, int b) => FromRaw(((long)a << Fixed64.Shift) / b);
        // Creates the fixed point number that's a divided by 10.
        [MethodImpl(FixedUtil.AggressiveInlining)] public static F64 Ratio10(int a) => FromRaw(((long)a << Fixed64.Shift) / 10);
        // Creates the fixed point number that's a divided by 100.
        [MethodImpl(FixedUtil.AggressiveInlining)] public static F64 Ratio100(int a) => FromRaw(((long)a << Fixed64.Shift) / 100);
        // Creates the fixed point number that's a divided by 1000.
        [MethodImpl(FixedUtil.AggressiveInlining)] public static F64 Ratio1000(int a) => FromRaw(((long)a << Fixed64.Shift) / 1000);

        // Operators
        [MethodImpl(MethodImplOptions.AggressiveInlining)] public static F64 operator -(F64 v1) => FromRaw(-v1.m_Raw);

        [MethodImpl(MethodImplOptions.AggressiveInlining)] public static F64 operator +(F64 v1, F64 v2) => FromRaw(v1.m_Raw + v2.m_Raw);
        [MethodImpl(MethodImplOptions.AggressiveInlining)] public static F64 operator -(F64 v1, F64 v2) => FromRaw(v1.m_Raw - v2.m_Raw);
        [MethodImpl(MethodImplOptions.AggressiveInlining)] public static F64 operator *(F64 v1, F64 v2) => FromRaw(Fixed64.Mul(v1.m_Raw, v2.m_Raw));
        [MethodImpl(MethodImplOptions.AggressiveInlining)] public static F64 operator /(F64 v1, F64 v2) => FromRaw(Fixed64.DivPrecise(v1.m_Raw, v2.m_Raw));
        [MethodImpl(MethodImplOptions.AggressiveInlining)] public static F64 operator %(F64 v1, F64 v2) => FromRaw(Fixed64.Mod(v1.m_Raw, v2.m_Raw));

        [MethodImpl(MethodImplOptions.AggressiveInlining)] public static F64 operator +(F64 v1, int v2) => FromRaw(v1.m_Raw + Fixed64.FromInt(v2));
        [MethodImpl(MethodImplOptions.AggressiveInlining)] public static F64 operator +(int v1, F64 v2) => FromRaw(Fixed64.FromInt(v1) + v2.m_Raw);
        [MethodImpl(MethodImplOptions.AggressiveInlining)] public static F64 operator -(F64 v1, int v2) => FromRaw(v1.m_Raw - Fixed64.FromInt(v2));
        [MethodImpl(MethodImplOptions.AggressiveInlining)] public static F64 operator -(int v1, F64 v2) => FromRaw(Fixed64.FromInt(v1) - v2.m_Raw);
        [MethodImpl(MethodImplOptions.AggressiveInlining)] public static F64 operator *(F64 v1, int v2) => FromRaw(Fixed64.Mul(v1.m_Raw, Fixed64.FromInt(v2)));
        [MethodImpl(MethodImplOptions.AggressiveInlining)] public static F64 operator *(int v1, F64 v2) => FromRaw(Fixed64.Mul(Fixed64.FromInt(v1), v2.m_Raw));
        [MethodImpl(MethodImplOptions.AggressiveInlining)] public static F64 operator /(F64 v1, int v2) => FromRaw(Fixed64.DivPrecise(v1.m_Raw, Fixed64.FromInt(v2)));
        [MethodImpl(MethodImplOptions.AggressiveInlining)] public static F64 operator /(int v1, F64 v2) => FromRaw(Fixed64.DivPrecise(Fixed64.FromInt(v1), v2.m_Raw));
        [MethodImpl(MethodImplOptions.AggressiveInlining)] public static F64 operator %(F64 v1, int v2) => FromRaw(Fixed64.Mod(v1.m_Raw, Fixed64.FromInt(v2)));
        [MethodImpl(MethodImplOptions.AggressiveInlining)] public static F64 operator %(int v1, F64 v2) => FromRaw(Fixed64.Mod(Fixed64.FromInt(v1), v2.m_Raw));

        [MethodImpl(MethodImplOptions.AggressiveInlining)] public static F64 operator ++(F64 v1) => FromRaw(v1.m_Raw + Fixed64.One);
        [MethodImpl(MethodImplOptions.AggressiveInlining)] public static F64 operator --(F64 v1) => FromRaw(v1.m_Raw - Fixed64.One);

        [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(F64 v1, F64 v2) => v1.m_Raw == v2.m_Raw;
        [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(F64 v1, F64 v2) => v1.m_Raw != v2.m_Raw;
        [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator <(F64 v1, F64 v2) => v1.m_Raw < v2.m_Raw;
        [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator <=(F64 v1, F64 v2) => v1.m_Raw <= v2.m_Raw;
        [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator >(F64 v1, F64 v2) => v1.m_Raw > v2.m_Raw;
        [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator >=(F64 v1, F64 v2) => v1.m_Raw >= v2.m_Raw;

        [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(int v1, F64 v2) => Fixed64.FromInt(v1) == v2.m_Raw;
        [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(F64 v1, int v2) => v1.m_Raw == Fixed64.FromInt(v2);
        [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(int v1, F64 v2) => Fixed64.FromInt(v1) != v2.m_Raw;
        [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(F64 v1, int v2) => v1.m_Raw != Fixed64.FromInt(v2);
        [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator <(int v1, F64 v2) => Fixed64.FromInt(v1) < v2.m_Raw;
        [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator <(F64 v1, int v2) => v1.m_Raw < Fixed64.FromInt(v2);
        [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator <=(int v1, F64 v2) => Fixed64.FromInt(v1) <= v2.m_Raw;
        [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator <=(F64 v1, int v2) => v1.m_Raw <= Fixed64.FromInt(v2);
        [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator >(int v1, F64 v2) => Fixed64.FromInt(v1) > v2.m_Raw;
        [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator >(F64 v1, int v2) => v1.m_Raw > Fixed64.FromInt(v2);
        [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator >=(int v1, F64 v2) => Fixed64.FromInt(v1) >= v2.m_Raw;
        [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator >=(F64 v1, int v2) => v1.m_Raw >= Fixed64.FromInt(v2);

        [MethodImpl(FixedUtil.AggressiveInlining)] public static F64 RadToDeg(F64 a) => FromRaw(Fixed64.Mul(a.m_Raw, 246083499198));  // 180 / F64.Pi
        [MethodImpl(FixedUtil.AggressiveInlining)] public static F64 DegToRad(F64 a) => FromRaw(Fixed64.Mul(a.m_Raw, 74961320));      // F64.Pi / 180

        [MethodImpl(FixedUtil.AggressiveInlining)] public static F64 Div2(F64 a) => FromRaw(a.m_Raw >> 1);
        [MethodImpl(FixedUtil.AggressiveInlining)] public static F64 Abs(F64 a) => FromRaw(Fixed64.Abs(a.m_Raw));
        [MethodImpl(FixedUtil.AggressiveInlining)] public static F64 Nabs(F64 a) => FromRaw(Fixed64.Nabs(a.m_Raw));
        [MethodImpl(FixedUtil.AggressiveInlining)] public static int Sign(F64 a) => Fixed64.Sign(a.m_Raw);
        [MethodImpl(FixedUtil.AggressiveInlining)] public static F64 Ceil(F64 a) => FromRaw(Fixed64.Ceil(a.m_Raw));
        [MethodImpl(FixedUtil.AggressiveInlining)] public static F64 Floor(F64 a) => FromRaw(Fixed64.Floor(a.m_Raw));
        [MethodImpl(FixedUtil.AggressiveInlining)] public static F64 Round(F64 a) => FromRaw(Fixed64.Round(a.m_Raw));
        [MethodImpl(FixedUtil.AggressiveInlining)] public static F64 Fract(F64 a) => FromRaw(Fixed64.Fract(a.m_Raw));
        [MethodImpl(FixedUtil.AggressiveInlining)] public static F64 Div(F64 a, F64 b) => FromRaw(Fixed64.Div(a.m_Raw, b.m_Raw));
        [MethodImpl(FixedUtil.AggressiveInlining)] public static F64 DivPrecise(F64 a, F64 b) => FromRaw(Fixed64.DivPrecise(a.m_Raw, b.m_Raw));
        [MethodImpl(FixedUtil.AggressiveInlining)] public static F64 DivFast(F64 a, F64 b) => FromRaw(Fixed64.DivFast(a.m_Raw, b.m_Raw));
        [MethodImpl(FixedUtil.AggressiveInlining)] public static F64 DivFastest(F64 a, F64 b) => FromRaw(Fixed64.DivFastest(a.m_Raw, b.m_Raw));
        [MethodImpl(FixedUtil.AggressiveInlining)] public static F64 SqrtPrecise(F64 a) => FromRaw(Fixed64.SqrtPrecise(a.m_Raw));
        [MethodImpl(FixedUtil.AggressiveInlining)] public static F64 Sqrt(F64 a) => FromRaw(Fixed64.Sqrt(a.m_Raw));
        [MethodImpl(FixedUtil.AggressiveInlining)] public static F64 SqrtFast(F64 a) => FromRaw(Fixed64.SqrtFast(a.m_Raw));
        [MethodImpl(FixedUtil.AggressiveInlining)] public static F64 SqrtFastest(F64 a) => FromRaw(Fixed64.SqrtFastest(a.m_Raw));
        [MethodImpl(FixedUtil.AggressiveInlining)] public static F64 RSqrt(F64 a) => FromRaw(Fixed64.RSqrt(a.m_Raw));
        [MethodImpl(FixedUtil.AggressiveInlining)] public static F64 RSqrtFast(F64 a) => FromRaw(Fixed64.RSqrtFast(a.m_Raw));
        [MethodImpl(FixedUtil.AggressiveInlining)] public static F64 RSqrtFastest(F64 a) => FromRaw(Fixed64.RSqrtFastest(a.m_Raw));
        [MethodImpl(FixedUtil.AggressiveInlining)] public static F64 Rcp(F64 a) => FromRaw(Fixed64.Rcp(a.m_Raw));
        [MethodImpl(FixedUtil.AggressiveInlining)] public static F64 RcpFast(F64 a) => FromRaw(Fixed64.RcpFast(a.m_Raw));
        [MethodImpl(FixedUtil.AggressiveInlining)] public static F64 RcpFastest(F64 a) => FromRaw(Fixed64.RcpFastest(a.m_Raw));
        [MethodImpl(FixedUtil.AggressiveInlining)] public static F64 Exp(F64 a) => FromRaw(Fixed64.Exp(a.m_Raw));
        [MethodImpl(FixedUtil.AggressiveInlining)] public static F64 ExpFast(F64 a) => FromRaw(Fixed64.ExpFast(a.m_Raw));
        [MethodImpl(FixedUtil.AggressiveInlining)] public static F64 ExpFastest(F64 a) => FromRaw(Fixed64.ExpFastest(a.m_Raw));
        [MethodImpl(FixedUtil.AggressiveInlining)] public static F64 Exp2(F64 a) => FromRaw(Fixed64.Exp2(a.m_Raw));
        [MethodImpl(FixedUtil.AggressiveInlining)] public static F64 Exp2Fast(F64 a) => FromRaw(Fixed64.Exp2Fast(a.m_Raw));
        [MethodImpl(FixedUtil.AggressiveInlining)] public static F64 Exp2Fastest(F64 a) => FromRaw(Fixed64.Exp2Fastest(a.m_Raw));
        [MethodImpl(FixedUtil.AggressiveInlining)] public static F64 Log(F64 a) => FromRaw(Fixed64.Log(a.m_Raw));
        [MethodImpl(FixedUtil.AggressiveInlining)] public static F64 LogFast(F64 a) => FromRaw(Fixed64.LogFast(a.m_Raw));
        [MethodImpl(FixedUtil.AggressiveInlining)] public static F64 LogFastest(F64 a) => FromRaw(Fixed64.LogFastest(a.m_Raw));
        [MethodImpl(FixedUtil.AggressiveInlining)] public static F64 Log2(F64 a) => FromRaw(Fixed64.Log2(a.m_Raw));
        [MethodImpl(FixedUtil.AggressiveInlining)] public static F64 Log2Fast(F64 a) => FromRaw(Fixed64.Log2Fast(a.m_Raw));
        [MethodImpl(FixedUtil.AggressiveInlining)] public static F64 Log2Fastest(F64 a) => FromRaw(Fixed64.Log2Fastest(a.m_Raw));

        [MethodImpl(FixedUtil.AggressiveInlining)] public static F64 Sin(F64 a) => FromRaw(Fixed64.Sin(a.m_Raw));
        [MethodImpl(FixedUtil.AggressiveInlining)] public static F64 SinFast(F64 a) => FromRaw(Fixed64.SinFast(a.m_Raw));
        [MethodImpl(FixedUtil.AggressiveInlining)] public static F64 SinFastest(F64 a) => FromRaw(Fixed64.SinFastest(a.m_Raw));
        [MethodImpl(FixedUtil.AggressiveInlining)] public static F64 Cos(F64 a) => FromRaw(Fixed64.Cos(a.m_Raw));
        [MethodImpl(FixedUtil.AggressiveInlining)] public static F64 CosFast(F64 a) => FromRaw(Fixed64.CosFast(a.m_Raw));
        [MethodImpl(FixedUtil.AggressiveInlining)] public static F64 CosFastest(F64 a) => FromRaw(Fixed64.CosFastest(a.m_Raw));
        [MethodImpl(FixedUtil.AggressiveInlining)] public static F64 Tan(F64 a) => FromRaw(Fixed64.Tan(a.m_Raw));
        [MethodImpl(FixedUtil.AggressiveInlining)] public static F64 TanFast(F64 a) => FromRaw(Fixed64.TanFast(a.m_Raw));
        [MethodImpl(FixedUtil.AggressiveInlining)] public static F64 TanFastest(F64 a) => FromRaw(Fixed64.TanFastest(a.m_Raw));
        [MethodImpl(FixedUtil.AggressiveInlining)] public static F64 Asin(F64 a) => FromRaw(Fixed64.Asin(a.m_Raw));
        [MethodImpl(FixedUtil.AggressiveInlining)] public static F64 AsinFast(F64 a) => FromRaw(Fixed64.AsinFast(a.m_Raw));
        [MethodImpl(FixedUtil.AggressiveInlining)] public static F64 AsinFastest(F64 a) => FromRaw(Fixed64.AsinFastest(a.m_Raw));
        [MethodImpl(FixedUtil.AggressiveInlining)] public static F64 Acos(F64 a) => FromRaw(Fixed64.Acos(a.m_Raw));
        [MethodImpl(FixedUtil.AggressiveInlining)] public static F64 AcosFast(F64 a) => FromRaw(Fixed64.AcosFast(a.m_Raw));
        [MethodImpl(FixedUtil.AggressiveInlining)] public static F64 AcosFastest(F64 a) => FromRaw(Fixed64.AcosFastest(a.m_Raw));
        [MethodImpl(FixedUtil.AggressiveInlining)] public static F64 Atan(F64 a) => FromRaw(Fixed64.Atan(a.m_Raw));
        [MethodImpl(FixedUtil.AggressiveInlining)] public static F64 AtanFast(F64 a) => FromRaw(Fixed64.AtanFast(a.m_Raw));
        [MethodImpl(FixedUtil.AggressiveInlining)] public static F64 AtanFastest(F64 a) => FromRaw(Fixed64.AtanFastest(a.m_Raw));
        [MethodImpl(FixedUtil.AggressiveInlining)] public static F64 Atan2(F64 y, F64 x) => FromRaw(Fixed64.Atan2(y.m_Raw, x.m_Raw));
        [MethodImpl(FixedUtil.AggressiveInlining)] public static F64 Atan2Fast(F64 y, F64 x) => FromRaw(Fixed64.Atan2Fast(y.m_Raw, x.m_Raw));
        [MethodImpl(FixedUtil.AggressiveInlining)] public static F64 Atan2Fastest(F64 y, F64 x) => FromRaw(Fixed64.Atan2Fastest(y.m_Raw, x.m_Raw));
        [MethodImpl(FixedUtil.AggressiveInlining)] public static F64 Pow(F64 a, F64 b) => FromRaw(Fixed64.Pow(a.m_Raw, b.m_Raw));
        [MethodImpl(FixedUtil.AggressiveInlining)] public static F64 PowFast(F64 a, F64 b) => FromRaw(Fixed64.PowFast(a.m_Raw, b.m_Raw));
        [MethodImpl(FixedUtil.AggressiveInlining)] public static F64 PowFastest(F64 a, F64 b) => FromRaw(Fixed64.PowFastest(a.m_Raw, b.m_Raw));

        [MethodImpl(FixedUtil.AggressiveInlining)] public static F64 Min(F64 a, F64 b) => FromRaw(Fixed64.Min(a.m_Raw, b.m_Raw));
        [MethodImpl(FixedUtil.AggressiveInlining)] public static F64 Max(F64 a, F64 b) => FromRaw(Fixed64.Max(a.m_Raw, b.m_Raw));
        [MethodImpl(FixedUtil.AggressiveInlining)] public static F64 Clamp(F64 a, F64 min, F64 max) => FromRaw(Fixed64.Clamp(a.m_Raw, min.m_Raw, max.m_Raw));
        [MethodImpl(FixedUtil.AggressiveInlining)] public static F64 Clamp01(F64 a) => FromRaw(Fixed64.Clamp(a.m_Raw, Fixed64.Zero, Fixed64.One));

        [MethodImpl(FixedUtil.AggressiveInlining)]
        public static F64 Lerp(F64 a, F64 b, F64 t) {
            long tb = t.m_Raw;
            long ta = Fixed64.One - tb;
            return FromRaw(Fixed64.Mul(a.m_Raw, ta) + Fixed64.Mul(b.m_Raw, tb));
        }

        public readonly bool Equals(F64 other) => m_Raw == other.m_Raw;

        public override readonly bool Equals(object obj) {
            if (obj is not F64) {
                return false;
            }
            return ((F64)obj).m_Raw == m_Raw;
        }

        public readonly int CompareTo(F64 other) {
            if (m_Raw < other.m_Raw) {
                return -1;
            }
            if (m_Raw > other.m_Raw) {
                return +1;
            }
            return 0;
        }

        public override readonly string ToString() => Fixed64.ToString(m_Raw);

        public override readonly int GetHashCode() => m_Raw.GetHashCode();

        readonly int IComparable.CompareTo(object obj) {
            if (obj is F64 other) {
                return ((IComparable<F64>)this).CompareTo(other);
            } else if (obj is null) {
                return 1;
            }
            // don't allow comparisons with other numeric or non-numeric types.
            throw new ArgumentException("F64 can only be compared against another F64.");
        }
    }
}
